/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
 * Description: Source File, hisec hwrand
 * Create: 2023/02/02
 */
#include <linux/types.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/kdebug.h>
#include <linux/rtnetlink.h>
#include <linux/proc_fs.h>
#include <linux/miscdevice.h>

#include "hinic3_hw.h"

/* sucurec include */
#include "securec.h"
#include "hisec_npu_cmd.h"
#include "hisec_npu_cmd_defs.h"
#include "hisec_hwrand.h"

/* Register this as a misc driver */
#define HW_RAND_PATH "/dev/hwrand"
#define HW_RAND_CLASS "hwrand_class"
#define HW_RAND_NAME "hwrand"

#define HW_RAND_CMDQ_MAX_BYTES 128
#define HW_RAND_CMDQ_MIN_BYTES 16
#define HW_RAND_READ_MAX_BYTES 256

#define HISEC_CDEV_CLASS_MODE ((umode_t)(S_IRUGO|S_IWUSR))

static struct hinic3_hwdev *ppf_hwdev;
static atomic_t g_hwrand_ref_cnt;
static dev_t g_dev_id = {0};
/*lint -save -e104 -e808*/
static struct class *g_hwrand_class;
/*lint -restore*/
static struct cdev g_hwrand_cdev;

static int file_open(struct inode* node, struct file* file)
{
    return 0;
}

static int file_close(struct inode* inode, struct file* file)
{
    return 0;
}

static int hisec_get_trng_from_npu(struct hinic3_hwdev *hwdev, size_t count, char *random_data)
{
    int ret = 0;
    size_t data_len;
    struct hinic3_cmd_buf *cmd_buf = NULL;
    hisec_cmd_trng_module_s *trng_cmd = NULL;

    cmd_buf = hinic3_alloc_cmd_buf(hwdev);
    if (!cmd_buf) {
        pr_err("Allocate cmd buf failed.\n");
        return -ENOMEM;
    }

    cmd_buf->size = sizeof(hisec_cmd_trng_module_s);
    trng_cmd = (hisec_cmd_trng_module_s *)cmd_buf->buf;
    trng_cmd->cmdhdr.dw0.bs.cmd_type = HISEC_NPU_CMD_HW_RANDOM;
    trng_cmd->cmdhdr.dw0.value = cpu_to_be32(trng_cmd->cmdhdr.dw0.value);

    do {
        data_len = (count > HW_RAND_CMDQ_MAX_BYTES) ? HW_RAND_CMDQ_MAX_BYTES: count;
        trng_cmd->length = (u32)ALIGN(data_len, HW_RAND_CMDQ_MIN_BYTES);
        trng_cmd->length = cpu_to_be32(trng_cmd->length);

        ret = hinic3_cmdq_detail_resp(hwdev, HINIC3_MOD_CRYPT, HISEC_NPU_CMD_HW_RANDOM, cmd_buf,
            cmd_buf, NULL, 0, HINIC3_CHANNEL_CRYPT);
        if (ret != 0) {
            break;
        }

        ret = memcpy_s(random_data, data_len, (trng_cmd->resp).data_out, data_len);
        if (ret != 0) {
            pr_err("Memcpy_s failed.\n");
            break;
        }
        random_data += data_len;

        count -= data_len;
    } while (count > 0);

    hinic3_free_cmd_buf(hwdev, cmd_buf);
    return ret;
}

static ssize_t file_read(struct file* file, char __user *buff, size_t count, loff_t *off)
{
    int err;
    u32 *random_data = NULL;

    if (count > HW_RAND_READ_MAX_BYTES) {
        pr_err("Invalid length");
        return 0;
    }

    random_data = (u32 *)vmalloc(HW_RAND_READ_MAX_BYTES);
    if (random_data == NULL) {
        pr_err("Vmalloc failed.");
        return 0;
    }
    err = hisec_get_trng_from_npu(ppf_hwdev, count, (char *)random_data);
    if (err != 0) {
        pr_err("get hw rand from npu failed, err 0x%x.\n", err);
        vfree(random_data);
        return 0;
    }

    if (copy_to_user(buff, random_data, (u32)count) != 0) {
        pr_err("hw rand copy to user failed!");
        vfree(random_data);
        return 0;
    }

    vfree(random_data);
    return (ssize_t)count;
}

/* Supported driver operations */
static const struct file_operations hw_rand_fops = {
    .open = file_open,
    .release = file_close,
    .read = file_read
};

static char *hisec_cdev_class_devnode(struct device *dev, umode_t *mode)
{
    if (mode != NULL)
        *mode = HISEC_CDEV_CLASS_MODE;
    return NULL;
}

int hisec_trng_init(struct hinic3_hwdev *hwdev)
{
    struct device *pdevice = NULL;
    int err;

    atomic_inc(&g_hwrand_ref_cnt);
    if (g_hwrand_ref_cnt.counter != 1) {
        /* already initialized */
        return 0;
    }
    ppf_hwdev = hwdev;

    err = alloc_chrdev_region(&g_dev_id, 0, 1, HW_RAND_NAME);
    if (err) {
        pr_err("Register hwrand failed(0x%x)\n", err);
        goto alloc_chdev_fail;
    }

    /* Create equipment */
    /*lint -save -e160*/
    g_hwrand_class = class_create(THIS_MODULE, HW_RAND_CLASS);
    /*lint -restore*/
    if (IS_ERR(g_hwrand_class)) {
        pr_err("Create hwrand_class fail\n");
        err = -EFAULT;
        goto class_create_err;
    }

    g_hwrand_class->devnode = hisec_cdev_class_devnode; //lint !e10 !e63 !e40

    /* Initializing the character device */
    cdev_init(&g_hwrand_cdev, &hw_rand_fops);

    /* Add devices to the operating system */
    err = cdev_add(&g_hwrand_cdev, g_dev_id, 1);
    if (err < 0) {
        pr_err("Add hwrand to operating system fail(0x%x)\n", err);
        goto cdev_add_err;
    }

    /* Export device information to user space
     * (/sys/class/class name/device name)
     */
    pdevice = device_create(g_hwrand_class, NULL,
                            g_dev_id, NULL, HW_RAND_NAME);
    if (IS_ERR(pdevice)) {
        pr_err("Export hwrand device information to user space fail\n");
        err = -EFAULT;
        goto device_create_err;
    }

    pr_info("Register hwrand to system succeed\n");

    return 0;

device_create_err:
    cdev_del(&g_hwrand_cdev);

cdev_add_err:
    class_destroy(g_hwrand_class);

class_create_err:
    g_hwrand_class = NULL;
    unregister_chrdev_region(g_dev_id, 1);

alloc_chdev_fail:
    atomic_dec(&g_hwrand_ref_cnt);

    return err;
}

void hisec_trng_deinit(void)
{
    if (!(g_hwrand_ref_cnt.counter)) {
        return;
    }

    if ((g_hwrand_ref_cnt.counter) != 1) {
        atomic_dec(&g_hwrand_ref_cnt);
        return;
    }

    if (!g_hwrand_class || IS_ERR(g_hwrand_class)) {
        pr_err("Hwrand class is NULL.\n");
        return;
    }

    device_destroy(g_hwrand_class, g_dev_id);
    cdev_del(&g_hwrand_cdev);
    class_destroy(g_hwrand_class);
    g_hwrand_class = NULL;

    unregister_chrdev_region(g_dev_id, 1);

    pr_info("Unregister hwrand succeed\n");
}